﻿#nullable enable

namespace NServiceBus.Features;

using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Pipeline;
using Settings;
using Transport;

/// <summary>
/// The context available to features when they are activated.
/// </summary>
public class FeatureConfigurationContext
{
    internal FeatureConfigurationContext(
        IReadOnlySettings settings,
        IServiceCollection container,
        PipelineSettings pipelineSettings,
        RoutingComponent.Configuration routing,
        ReceiveComponent.Configuration receiveConfiguration,
        PersistenceComponent.Configuration persistenceConfiguration)
    {
        this.persistenceConfiguration = persistenceConfiguration;
        Settings = settings;
        Services = container;
        Pipeline = pipelineSettings;
        Routing = routing;
        Receiving = receiveConfiguration;

        TaskControllers = [];
    }

    /// <summary>
    /// A read only copy of the settings.
    /// </summary>
    public IReadOnlySettings Settings { get; }

    /// <summary>
    /// Access to the <see cref="IServiceCollection"/> to allow additional service registrations.
    /// </summary>
    public IServiceCollection Services { get; }

    /// <summary>
    /// Access to the pipeline in order to customize it.
    /// </summary>
    public PipelineSettings Pipeline { get; }

    internal RoutingComponent.Configuration Routing { get; }

    internal ReceiveComponent.Configuration Receiving => field ?? throw new InvalidOperationException("Receive component is not enabled since this endpoint is configured to run in send-only mode.");

    internal List<IFeatureStartupTaskController> TaskControllers { get; }

    internal bool HasSupportForStorage<TStorage>() where TStorage : StorageType, new() => persistenceConfiguration.SupportedPersistences.Contains<TStorage>();
    internal TOptions? GetStorageOptions<TOptions>() where TOptions : StorageType.Options => persistenceConfiguration.SupportedPersistences.Get<TOptions>();

    /// <summary>
    /// Adds a new satellite receiver.
    /// </summary>
    /// <param name="name">Name of the satellite.</param>
    /// <param name="transportAddress">The autogenerated transport address to listen on.</param>
    /// <param name="runtimeSettings">Transport runtime settings.</param>
    /// <param name="recoverabilityPolicy">Recoverability policy to be if processing fails.</param>
    /// <param name="onMessage">The message func.</param>
    public void AddSatelliteReceiver(string name, QueueAddress transportAddress, PushRuntimeSettings runtimeSettings, Func<RecoverabilityConfig, ErrorContext, RecoverabilityAction> recoverabilityPolicy, OnSatelliteMessage onMessage)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(name);
        ArgumentNullException.ThrowIfNull(transportAddress);
        ArgumentNullException.ThrowIfNull(runtimeSettings);
        ArgumentNullException.ThrowIfNull(recoverabilityPolicy);
        ArgumentNullException.ThrowIfNull(onMessage);

        Receiving.AddSatelliteReceiver(name, transportAddress, runtimeSettings, recoverabilityPolicy, onMessage);
    }

    /// <summary>
    /// Registers a feature startup task.
    /// </summary>
    /// <remarks>The startup task will automatically have all it's constructor parameters resolved from the dependency injection container.</remarks>
    /// <typeparam name="TTask">The startup task type to register.</typeparam>
    public void RegisterStartupTask<TTask>() where TTask : FeatureStartupTask
        => TaskControllers.Add(new ActivatorUtilityBasedFeatureStartupTaskController<TTask>());

    /// <summary>
    /// Registers an instance of a feature startup task.
    /// </summary>
    /// <param name="startupTask">A startup task.</param>
    public void RegisterStartupTask<TTask>(TTask startupTask) where TTask : FeatureStartupTask
    {
        ArgumentNullException.ThrowIfNull(startupTask);
        TaskControllers.Add(new FeatureStartupTaskController<TTask>(typeof(TTask).Name, static (_, task) => task, startupTask));
    }

    /// <summary>
    /// Registers a startup task factory.
    /// </summary>
    /// <param name="startupTaskFactory">A startup task factory.</param>
    public void RegisterStartupTask<TTask>(Func<TTask> startupTaskFactory) where TTask : FeatureStartupTask
    {
        ArgumentNullException.ThrowIfNull(startupTaskFactory);
        TaskControllers.Add(new FeatureStartupTaskController<Func<TTask>>(typeof(TTask).Name, static (_, startupTaskFactory) => startupTaskFactory(), startupTaskFactory));
    }

    /// <summary>
    /// Registers a startup task factory which gets access to the builder.
    /// </summary>
    /// <param name="startupTaskFactory">A startup task factory.</param>
    /// <remarks>Should only be used when really necessary. Usually a design smell.</remarks>
    public void RegisterStartupTask<TTask>(Func<IServiceProvider, TTask> startupTaskFactory) where TTask : FeatureStartupTask
    {
        ArgumentNullException.ThrowIfNull(startupTaskFactory);
        TaskControllers.Add(new FeatureStartupTaskController(typeof(TTask).Name, startupTaskFactory));
    }

    readonly PersistenceComponent.Configuration persistenceConfiguration;
}